/**
 * Java RTP Library (jlibrtp)
 * Copyright (C) 2006 Arne Kepp
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
package com.detrum.sportcamera.jlibrtp;

/**
 * A PktBuffer stores packets either for buffering purposes,
 * or because they need to be assimilated to create a complete frame.
 * 
 * This behavior can be controlled through rtpSession.pktBufBehavior()
 * 
 * It optionally drops duplicate packets.
 * 
 * Note that newest is the most recently received, i.e. highest timeStamp
 * Next means new to old (from recently received to previously received) 
 * 
 * @author Arne Kepp
 */

public class PktBuffer {

	/** The RTPSession holds information common to all packetBuffers, such as max size */
	final int pktBufBehavior = 3;
	/** The length of the buffer */
	int length = 0;

	/** The oldest, least recently received, packet */
    FrameNode oldest = null;
	/** The newest, most recently received, packet */
    FrameNode newest = null;
	
	/** The last sequence number received */
	int lastSeqNumber = -1;
	/** The last timestamp */
	long lastTimestamp = -1;

	public PktBuffer() {
		oldest = null;
		newest = null;
		//lastSeqNumber = (aPkt.getSeqNumber() - 1);
		//lastTimestamp = aPkt.getTimeStamp();
		length = 0;
	}

	/**
	 * Adds a packet, this happens in constant time if they arrive in order.
	 * Optimized for the case where each pkt is a complete frame.
	 * 
	 * @param aPkt the packet to be added to the buffer.
	 * @return integer, negative if operation failed (see code)
	 */
	public synchronized int addPkt(RtpPkt aPkt) {
        PktBufNode newNode = new PktBufNode(aPkt);
        return bufferedAddPkt(newNode);
	}

	private int drop_oldest_frame(){
        FrameNode tmpnode = oldest;
        if(tmpnode != null){
            oldest = tmpnode.prevFrame ;
            oldest.nextFrame = null;

            length --;
        }
        return 0;
    }

	/**
	 * Does most of the packet organization for the application.
	 * Packets are put in order, duplicate packets or late arrivals are discarded
	 * 
	 * If multiple packets make up a frame, these will also be organized
	 * by RTP timestamp and sequence number, and returned as a complete frame.
	 * 
	 * @param newNode the node to add to the packet buffer
	 * @return 0 if everything is okay, -1 otherwise
	 */
	private int bufferedAddPkt(PktBufNode newNode) {

        FrameNode newFrame = null;
		if(length == 0) {
            newFrame = new FrameNode(newNode.timeStamp, newNode.PayloadType);

			// The buffer was empty, this packet is the one and only.
			newest = newFrame;
			oldest = newFrame;

            length ++;

            return newFrame.addPktToFrame(newNode);
		}

		// The packetbuffer is not empty.
		if(newNode.timeStamp > newest.timeStamp) {
            if(length == pktBufBehavior) {
                drop_oldest_frame();
            }

            newFrame = new FrameNode(newNode.timeStamp,newNode.PayloadType);

            newFrame.nextFrame = newest;
            newest.prevFrame = newFrame;
            newest = newFrame;

            length ++;

            return newFrame.addPktToFrame(newNode);
		} else {
			//Need to do some real work, find out where it belongs (linear search from the back).
			FrameNode tmpNode = newest;
			
			while(tmpNode != null) {
				
				if(tmpNode.timeStamp > newNode.timeStamp) {
					tmpNode = tmpNode.nextFrame;
					continue;
				}

				if( tmpNode.timeStamp == newNode.timeStamp ) {
					//Packet has same timestamp, presumably belongs to frame. Need to order within frame.
                	return tmpNode.addPktToFrame(newNode);
				}

				break;
			}
				
            if(length == pktBufBehavior) {
                drop_oldest_frame();
            }

            newFrame = new FrameNode(newNode.timeStamp,newNode.PayloadType);

			if(tmpNode == null)
			{
				newFrame.prevFrame = oldest;
				oldest.nextFrame = newFrame;

				oldest = newFrame;
			}else {
				// Insert after tmpNode
	            newFrame.nextFrame = tmpNode;
	            newFrame.prevFrame = tmpNode.prevFrame;
				// Update the node behind
				if(newFrame.prevFrame != null) {
	                newFrame.prevFrame.nextFrame = newFrame;
				}
	            tmpNode.prevFrame = newFrame;
			}

			if( (newFrame.timeStamp > newest.timeStamp)
                   || ( (newFrame.timeStamp < 0xFFF ) && (newest.timeStamp > 0xFFFFF000) )
                 ) {
                newFrame.nextFrame = newest;
                newest.prevFrame = newFrame;
                newest = newFrame;
			}
            length++;

            return newFrame.addPktToFrame(newNode);
		}
	}

	/** 
	 * Checks the oldest frame, if there is one, sees whether it is complete.
	 * @return Returns null if there are no complete frames available.
	 */
	public synchronized FrameNode popOldestFrame() {
		return this.bufferedPopFrame();
	}

	/**
	 * Only returns if the buffer is full, i.e. length exceeds
	 * rtpSession.pktBufBehavior, or if the next packet directly
	 * follows the previous one returned to the application.
	 * 
	 * @return first frame in order, null otherwise
	 */
	private FrameNode bufferedPopFrame() {
		FrameNode frameNode = oldest;
		/**
		 * Three scenarios:
		 * 1) There are no packets available
		 * 2) The first packet is vailable and in order
		 * 3) The first packet is not the next on in the sequence
		 * 		a) We have exceeded the wait buffer
		 * 		b) We wait
		 */
		//System.out.println(" Debug:" +(retNode != null) + " " + (retNode.seqNum == this.lastSeqNumber + 1)
		//		+ " " + ( retNode.seqNum == 0 ) + " " +  (this.length > this.rtpSession.maxReorderBuffer)
		//		+ " " + (this.lastSeqNumber < 0));

		// Pop it off, null all references.
        while(frameNode !=null ){
            if( frameNode.isComplete == true ) {
                popFrameCleanup(frameNode);
                return frameNode;
            }
            frameNode = frameNode.prevFrame;
        }

		return null;
	}
	
	/**
	 * Cleans the packet buffer before returning the frame,
	 * i.e. making sure the queue has a head etc.
	 */
	private void popFrameCleanup(FrameNode frameNode) {
		if(1 == length) {
			//There's only one frame
			newest = null;
			oldest = null;
		} else {
			//There are more frames
			oldest = oldest.prevFrame;
			oldest.nextFrame = null;
		}

		// Update counters
		length--;
	}
	
	/** 
	 * Returns the length of the packetbuffer.
	 * @return number of frames (complete or not) in packetbuffer.
	 */
	protected int getLength() {
		return length;
	}
	
	/**
	 * Checks whether a packet is not too late, i.e. the next packet has already been returned.
	 * @param timeStamp the RTP timestamp of the packet under consideration 
	 * @param seqNum the sequence number of the packet under consideration
	 * @return true if newer packets have not been handed to the application
	 */
	protected boolean pktOnTime(long timeStamp, int seqNum) {
		if(this.lastSeqNumber == -1) {
			// First packet
			return true;
		} else {			
			if(seqNum >= this.lastSeqNumber) {
				if(this.lastSeqNumber < 3 && timeStamp < this.lastTimestamp ) {
					return false;
				}
			} else {
				if(seqNum > 3 || timeStamp < this.lastTimestamp) {
					return false;
				}
			}
		}
		return true;
	}
}
